package blazingbubble.legacy.game;

import blazingbubble.legacy.Point;

import java.util.ArrayList;
import java.util.Vector;

import spark.math.Vector3;

public class BubbleGrid
{
	private static class PositionSet
	{
		private int gridPosition;
		private float worldPosition;

		public PositionSet(int gridPosition, float worldPosition)
		{
			super();
			this.gridPosition = gridPosition;
			this.worldPosition = worldPosition;
		}
		public int getGridPosition()
		{
			return gridPosition;
		}
		public void setGridPosition(int gridPosition)
		{
			this.gridPosition = gridPosition;
		}
		public float getWorldPosition()
		{
			return worldPosition;
		}
		public void setWorldPosition(float worldPosition)
		{
			this.worldPosition = worldPosition;
		}
	}
	/**
	 * Distance from the left/right gamefield border on indented rows (bubble radius)
	 */
	private final float XINDENT;

	/**
	 * Amount by which bubbles are interleaved on their y positions (y distance from center-to-center)
	 */
	private final float YSTEP;
	private boolean firstIndented = false;

	private ArrayList<Bubble[]> grid;

	public BubbleGrid(int height, int width, float radius)
	{
		XINDENT = radius;
		YSTEP = (radius * 2)* 0.875f;
		grid = new ArrayList<Bubble[]>(height + 1);
		for(int i = 0; i < height + 1; i++)
		{
			grid.add(new Bubble[width - (isRowIndented(i) ?1 :0)]);
		}
	}

	/**
	 * Figures out the grid and according world coordinates for a bubble and attaches it to the grid.
	 *
	 * @param bubble Bubble to attach
	 */
	public void snapToGrid(Bubble bubble)
	{
		//figure out the grid coordinates for the bubble and the according world coordinates
		PositionSet y = fixGridY(bubble.getPosition().y);
		PositionSet x = fixGridX(bubble.getPosition().x, isRowIndented(y.getGridPosition()));
		//apply them
		bubble.getPosition().x = x.getWorldPosition();
		bubble.getPosition().y = y.getWorldPosition();
		bubble.setGridPositionX(x.getGridPosition());
		bubble.setGridPositionY(y.getGridPosition());

		attach(bubble);
	}

	/**
	 * Attaches a bubble to a desired grid coordinate fixing the bubbles world coordinates
	 * accordingly, the old word coordinates are discarded and the new ones applied instead.
	 *
	 * @param bubble Bubble to attach
	 * @param gridPositionX Valid and empty grid coordinate
	 * @param gridPositionY Valid and empty grid coordinate
	 */
	public void snapToGrid(Bubble bubble, int gridPositionX, int gridPositionY)
	{
		boolean isIndented = isRowIndented(gridPositionY);

		bubble.setGridPositionY(gridPositionY);
		bubble.getPosition().y = gridPositionY * YSTEP;

		bubble.setGridPositionX(gridPositionX);
		if(isIndented)
		{
			bubble.getPosition().x = gridPositionX + 1;
		}
		else
		{
			bubble.getPosition().x = gridPositionX + XINDENT;
		}

		attach(bubble);
	}

	/**
	 * Attaches a prepared bubble to the grid and links it to its neighbours.
	 * Prepared means that the bubbles grid and world coordinates have already been set accordingly.
	 *
	 * @param bubble A bubble of which the grid and world coordinates have already been set
	 */
	private void attach(Bubble bubble)
	{
		grid.get(bubble.getGridPositionY())[bubble.getGridPositionX()] = bubble;

		boolean isIndented = isRowIndented(bubble.getGridPositionY());

		if(bubble.getGridPositionY() == grid.size() - 1)
		{
			bubble.setDirectlyAttachedToWorld(true);
		}

		//attach the bubble to its neigbours
		Point[] neighbours = new Point[6];
		//Left & Right
		neighbours[0] = new Point(bubble.getGridPositionX() - 1, bubble.getGridPositionY());
		neighbours[1] = new Point(bubble.getGridPositionX() + 1, bubble.getGridPositionY());
		//LeftTop & RightTop
		neighbours[2] = new Point(bubble.getGridPositionX(), bubble.getGridPositionY() - 1);
		neighbours[3] = new Point(bubble.getGridPositionX() + (isIndented ?1 :-1), bubble.getGridPositionY() - 1);
		//LeftBottom & RightBottom
		neighbours[4] = new Point(bubble.getGridPositionX(), bubble.getGridPositionY() + 1);
		neighbours[5] = new Point(bubble.getGridPositionX() + (isIndented ?1 :-1), bubble.getGridPositionY() + 1);

        for(Point neighbour1 : neighbours)
        {
            int x = neighbour1.x;
            int y = neighbour1.y;
            if(!isValid(x, y))
            {
                continue;
            }
            Bubble neighbour = getBubble(x, y);
            if(neighbour != null)
            {
                neighbour.attach(bubble);
            }
        }
	}

	public Vector<Point> getEmptyNeighbourCells(int x, int y)
	{
		boolean isIndented = isRowIndented(y);

		Point[] neighbours = new Point[6];
		//Left & Right
		neighbours[0] = new Point(x - 1, y);
		neighbours[1] = new Point(x + 1, y);
		//LeftTop & RightTop
		neighbours[2] = new Point(x, y - 1);
		neighbours[3] = new Point(x + (isIndented ?1 :-1), y - 1);
		//LeftBottom & RightBottom
		neighbours[4] = new Point(x, y + 1);
		neighbours[5] = new Point(x + (isIndented ?1 :-1), y + 1);

		Vector<Point> emptyNeighbourCells = new Vector<Point>();
        for(Point neighbour : neighbours)
        {
            if(!isValid(neighbour.x, neighbour.y))
            {
                continue;
            }
            if(getBubble(neighbour.x, neighbour.y) == null)
            {
                emptyNeighbourCells.add(neighbour);
            }
        }
		return emptyNeighbourCells;
	}

	public Vector3 getWorldCoordinates(int x, int y)
	{
		Vector3 worldCoordinates = new Vector3();
		if(isRowIndented(y))
		{
			worldCoordinates.x = x + 1;
		}
		else
		{
			worldCoordinates.x = x + XINDENT;
		}
		worldCoordinates.y = y * YSTEP;
		return worldCoordinates;
	}

	private PositionSet fixGridY(float worldY)
	{
		float closestGridYPosition = worldY / YSTEP;
		if(closestGridYPosition - (int)closestGridYPosition >= 0.5)
			closestGridYPosition += 1;
		return new PositionSet((int)closestGridYPosition, (int)closestGridYPosition * YSTEP);
	}

	private PositionSet fixGridX(float worldX, boolean indented)
	{
		float closestGridXPosition;
		float newWorldX;

		if(indented)
		{
			worldX -= XINDENT;
			closestGridXPosition = (int)worldX;
			newWorldX = closestGridXPosition + 1;
		}
		else
		{
			closestGridXPosition = (int)worldX;
			newWorldX = (int)closestGridXPosition + XINDENT;
		}
		return new PositionSet((int)closestGridXPosition, newWorldX);
	}

	private boolean isRowIndented(int gridY)
	{
        return (gridY % 2 == 0) == firstIndented;
	}

	public void resetField(int x, int y)
	{
		grid.get(y)[x] = null;
	}

	public boolean isValid(int x, int y)
	{
        return !(y < 0 || y > (grid.size() - 1) || x < 0 || (x > grid.get(y).length - 1));
    }

	public Bubble getBubble(int x, int y)
	{
		return grid.get(y)[x];
	}
}
